#pragma once

#include <list>
#include <map>
#include <string>
#include <iostream>	
#include <map>

/*
	TODO:
		make it so that if a shout message data variable changes type 
		then an error is displayed at the handlers that use the old variable
*/

using namespace std;
struct MessageRouter;

#if 0
/*
all keys should be lowercase with _ between words
e.g. entity_list
*/
struct MessageData
{
	std::map<string, int> _ints;
	std::map<string, float> _floats;

	//["router"] returns the MessageRouter
	std::map<string, void *> _ptrs;
	std::map<string, string> _strings;
};


static inline MessageData * MD_Add(MessageData * md,string key, int value){
	if(md)
	md->_ints[key] = value;
	return md;
}

static inline MessageData * MD_Add(MessageData * md,string key, float value){
		if(md)
	md->_floats[key] = value;
	return md;
}

static inline MessageData * MD_Add(MessageData * md,string key, void * ptr){
		if(md)
	md->_ptrs[key] = ptr;
	return md;
}

static inline MessageData * MD_Add(MessageData * md,string key, string value){
		if(md)
	md->_strings[key] = value;
	return md;
}

static inline MessageData * MD_Get(MessageData * md, string key, int & out_value){
	if (md ){
		if (md->_ints.find(key) != md->_ints.end()){
			out_value = md->_ints[key];
		}
	}
	return md;
}

static inline MessageData * MD_Get(MessageData * md, string key, float & out_value){
	if (md){
		if (md->_floats.find(key) != md->_floats.end()){
			out_value = md->_floats[key];
		}
	}
	return md;
}

static inline MessageData * MD_Get(MessageData * md, string key, void* & out_value){
	if (md ){
		if (md->_ptrs.find(key) != md->_ptrs.end()){
			out_value = md->_ptrs[key];
		}
	}
	return md;
}

static inline MessageData * MD_Get(MessageData * md, string key, string & out_value){
	if (md ){
		if (md->_strings.find(key) != md->_strings.end()){
			out_value = md->_strings[key];
		}
	}
	return md;
}

#endif
struct MessagePacket
{
	MessageRouter * router;
	void * sender;
	string message;
	void * data;
	//MessageData * meta;
};

typedef MessagePacket * (*MessageCallbackFnPtr)(MessagePacket * packet);

struct MessageRouter
{
	//list of all general fns
	//list<MessageCallbackFnPtr> all_fn_ptrs;

	//list of fns that are listening for a certain event//
	map<string,list<MessageCallbackFnPtr>> type_catorgory;
};

 static void MessageRouter_RegisterTriggerCallback(MessageRouter * mr, MessageCallbackFnPtr fn, string trigger)
{
	mr->type_catorgory[trigger].push_back(fn);
}

//const MessageData MessageData_null = {};

//sends to all triggered callbacks
// TODO: returns a list<MessagePacket*>
static list<MessagePacket *> MessageRouter_SpawnTriggeredMessage(MessageRouter * mr,void * sender,  
	string message_type,void * data = 0)//,  MessageData * meta  = 0)
{
	MessagePacket * mp = new MessagePacket();
	mp->data = data;
	mp->message = message_type;
	//mp->meta = meta;
	mp->router = mr;
	mp->sender = sender;

	list<MessagePacket*> Result;
	//MD_Add(&md, "router", (void*)mr);

	list<MessageCallbackFnPtr> callbacks = mr->type_catorgory[message_type];

	list<MessageCallbackFnPtr>::iterator message_iter;
	for (message_iter = callbacks.begin();
		message_iter != callbacks.end();
		message_iter++)
	{
		Result.push_back((*message_iter)(mp));
	}
	delete mp;
	return Result;
}

static void MessageRouter__Clear(MessageRouter * mr)
{
	for (map<string,list<MessageCallbackFnPtr>>::iterator it = mr->type_catorgory.begin();
		it != mr->type_catorgory.end(); ++it)
	{
		std::string s = ((*it).first);s.clear();
		(*it).second.clear();
	}
	mr->type_catorgory.clear();
}

static void Shout(MessageRouter * router, void * sender, string message_type, void * data = 0)
{
	MessagePacket * mp = new MessagePacket();
	mp->data = data;
	mp->message = message_type;
	mp->router = router;
	mp->sender = sender;

	list<MessageCallbackFnPtr> callbacks = router->type_catorgory[message_type];

	list<MessageCallbackFnPtr>::iterator message_iter;
	for (message_iter = callbacks.begin();
		message_iter != callbacks.end();
		message_iter++)
	{
		(*message_iter)(mp);
	}
	delete mp;
}